﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Collections.ObjectModel;
using System.Windows.Forms;

namespace ChartEdit
{
    public class Chart
    {
        static readonly Regex TrackRegex = new Regex(
            @"\[(?<title>[a-zA-Z]+)\]\r\n" + @"\{\r\n" + @"(\s(?<entry>.+?)\r\n)+" + @"\}",
            RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline
        );

        public string Name { get; set; }
        public SongSection Song { get; set; }
        public SyncTrackSection SyncTrack { get; set; }
        public EventsSection Events { get; set; }

        public NoteTracks NoteTracks { get; set; }

        public float SongLength { get; set; }
        public int LastIndex { get; set; }

        public string LoadPath { get; set; }

        #region Chart Resolution Definitions
        private int res;
        public int Resolution
        {
            set
            {
                res = value;
                hRes = value / 2;
                qRes = value / 4;
                eRes = value / 8;
                sRes = value / 16;
                tRes = value / 32;
            }
            get
            {
                return res;
            }
        }
        private int hRes;
        public int HalfResolution { get { return hRes; } }
        private int qRes;
        public int QuarterResolution { get { return qRes; } }
        private int eRes;
        public int EighthResolution { get { return eRes; } }
        private int sRes;
        public int SixteenthResolution { get { return sRes; } }
        private int tRes;
        public int ThirtysecondResolution { get { return tRes; } }
        #endregion
        public void GetResolution()
        {
            if (Song.ContainsKey("Resolution"))
            {
                Resolution = Int32.Parse(Song["Resolution"].Value);
                return;
            }
            Resolution = 192;
        }


        #region Constructors
        public Chart()
        {
            Song = new SongSection();
            SyncTrack = new SyncTrackSection();
            Events = new EventsSection();

            NoteTracks = new NoteTracks();
        }
        public Chart(string fileName)
        {
            Song = new SongSection();
            SyncTrack = new SyncTrackSection();
            Events = new EventsSection();

            NoteTracks = new NoteTracks();

            Load(fileName);
        } 
        #endregion

        public void HerpDerp(Note derpAHerp, string trackIndex)
        {
            this.NoteTracks[trackIndex].Insert(0, derpAHerp);
        }

        public void Load( string fileName )
        {
            if ( !File.Exists( fileName ) )
                throw new FileNotFoundException( fileName + " was not found." );

            string chartData = File.ReadAllText( fileName );

            if ( string.IsNullOrEmpty( chartData ) )
                throw new IOException( "Unable to load chart file." );


            FileInfo fi = new FileInfo( fileName );
            LoadPath = fi.Directory.FullName;

            ParseChartData( chartData );

            //Set Important Key Values
            GetResolution();
            
        }

        public void Save( string fileName )
        {
            StringBuilder chartData = new StringBuilder();

            WriteSongData( chartData );
            WriteSyncTrackData( chartData );
            WriteEventData( chartData );

            foreach ( NoteTrack noteTrack in NoteTracks )
            {
                WriteNoteTrackData( chartData, noteTrack );
            }

            File.WriteAllText( fileName, chartData.ToString() );
        }

        private void WriteNoteTrackData( StringBuilder chartData, NoteTrack noteTrack )
        {
            chartData.Append( "[" + noteTrack.Name + "]\r\n" );
            chartData.Append( "{\r\n" );

            foreach ( Note note in noteTrack )
            {
                chartData.Append( "\t" + note.Offset + " = " );

                switch ( note.Type )
                {

                    case NoteType.Regular:
                        chartData.Append( "N " + note.Fret + " " + note.Length );
                        break;

                    case NoteType.Special:
                        chartData.Append( "S " + note.SpecialFlag + " " + note.Length );
                        break;

                    case NoteType.Event:
                        chartData.Append( "E " + note.EventName );
                        break;

                }

                chartData.Append( "\r\n" );
            }

            chartData.Append( "}\r\n" );
        }
        private void WriteEventData( StringBuilder chartData )
        {
            chartData.Append( "[Events]\r\n" );
            chartData.Append( "{\r\n" );

            foreach ( EventsSectionEntry eventEntry in Events )
            {
                chartData.Append( "\t" + eventEntry.Offset + " = " );

                switch ( eventEntry.Type )
                {
                    case EventType.Effect:
                        chartData.Append( "H " + eventEntry.EffectType + " " + eventEntry.EffectLength );
                        break;

                    case EventType.Text:
                        chartData.Append( "E \"" + eventEntry.TextValue + "\"" );
                        break;
                }

                chartData.Append( "\r\n" );
            }

            chartData.Append( "}\r\n" );
        }
        private void WriteSyncTrackData( StringBuilder chartData )
        {

            chartData.Append( "[SyncTrack]\r\n" );
            chartData.Append( "{\r\n" );

            foreach ( SyncTrackEntry syncEntry in SyncTrack )
            {
                chartData.Append( "\t" + syncEntry.Offset + " = " );

                switch ( syncEntry.Type )
                {
                    case SyncType.Anchor:
                        chartData.Append( "A " + syncEntry.Anchor );
                        break;

                    case SyncType.BPM:
                        chartData.Append( "B " + syncEntry.BPM );
                        break;

                    case SyncType.TimeSignature:
                        chartData.Append( "TS " + syncEntry.TimeSignature );
                        break;
                }

                chartData.Append( "\r\n" );
            }
            chartData.Append( "}\r\n" );
        }
        private void WriteSongData( StringBuilder chartData )
        {
            chartData.Append( "[Song]\r\n" );
            chartData.Append( "{\r\n" );

            foreach ( SongSectionEntry songEntry in Song )
            {
                chartData.Append( "\t" + songEntry.Key + " = " + songEntry.Value + "\r\n" );
            }

            chartData.Append( "}\r\n" );
        }


        private void ParseChartData( string chartData )
        {
            MatchCollection dataMatches = TrackRegex.Matches( chartData );

            foreach ( Match match in dataMatches )
            {
                Group title = match.Groups[ "title" ];
                Group entry = match.Groups[ "entry" ];

                string titleStr = title.Value.Trim();

                foreach ( Capture capture in entry.Captures )
                {
#if DEBUG
                    bool ret = 
#endif

                    HandleKeyValue( titleStr, capture.Value );

#if DEBUG
                    if ( !ret )
                    {
                        MessageBox.Show( "Error handling keyvalue.\n\ntitle: " + titleStr + "\nentry: " + capture.Value );
                        continue;
                    }
#endif
                }
            }
        }

        private bool HandleKeyValue( string title, string entry )
        {
            if ( title == SongSection.SectionName )
            {
                return HandleSong( entry );
            }
            else if ( title == SyncTrackSection.SectionName )
            {
                return HandleSyncTrack( entry );
            }
            else if ( title == EventsSection.SectionName )
            {
                return HandleEvents( entry );
            }

            return HandleNote( title, entry );
        }


        private bool HandleNote( string title, string entry )
        {
            Note note = Note.Parse( entry );

            if ( note == null )
                return false;

            if ( !NoteTracks.ContainsTrack( title ) )
            {
                NoteTracks.Add( new NoteTrack( title ) );
            }

            NoteTrack track = NoteTracks[ title ];

            if ( note.Offset > LastIndex )
                LastIndex = note.Offset;

            track.Add( note );

            return true;
        }

        private bool HandleSong( string entry )
        {
            SongSectionEntry songEntry = SongSectionEntry.Parse( entry );

            if ( songEntry == null )
                return false;

            Song.Add( songEntry );

            return true;
        }

        private bool HandleSyncTrack( string entry )
        {
            SyncTrackEntry syncEntry = SyncTrackEntry.Parse( entry );

            if ( syncEntry == null )
                return false;

            if ( syncEntry.Offset > LastIndex )
                LastIndex = syncEntry.Offset;

            SyncTrack.Add( syncEntry );

            return true;
        }

        private bool HandleEvents( string entry )
        {
            EventsSectionEntry eventEntry = EventsSectionEntry.Parse( entry );

            if ( eventEntry == null )
                return false;

            if ( eventEntry.Offset > LastIndex )
                LastIndex = eventEntry.Offset;

            Events.Add( eventEntry );

            return true;
        }
    }
}
